//###############
//FILE : RequestThreadDispatcher.java
//WRITER : Daniel Lowengrub, daniel_i_l, 320687098
//EXERCISE : oop ex2 2011
//DESCRIPTION:
//See below
//###############

package oop.ex3.communication;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;

/**
 * This class wait for connections on a certain address and creates new request
 * thread to handle each one. For each new request it make a copy of the specified
 * base thread and uses it to handle the request. It holds a server status object
 * which can be used by other objects to control this thread.
 * The dispatcher is also in charge of closing all open connections on shutdown.
 * Each request thread is in charge of removing itself from the thread pool when it's finished.
 * @author daniel
 */
public class RequestThreadDispatcher extends Thread {
    private SocketAddress _address;
    private ServerStatus _status;
    private ArrayList<RequestThread> _threadPool;
    private RequestThread _baseThread;
    private ServerSocket _serverSocket;

    /**
     * Create a dispatcher that listens on a specified address and uses copies of
     * the specified request thread.
     * @param address The address to listen to.
     * @param baseThread The request thread to use for handling requests.
     */
    public RequestThreadDispatcher(SocketAddress address,
            RequestThread baseThread) {
        _address = address;
        _status = new ServerStatus();
        _threadPool = new ArrayList<RequestThread>();
        _baseThread = baseThread;
    }

    /**
     * Create a dispatcher that listens on a specified address.
     * @param address The address to listen to.
     */
    public RequestThreadDispatcher(SocketAddress address) {
        _address = address;
        _status = new ServerStatus();
        _threadPool = new ArrayList<RequestThread>();
    }

    /**
     * Set the request thread used for requests.
     * @param thread The request thread.
     */
    public void setBaseThread(RequestThread thread) {
        _baseThread = thread;
    }

    /**
     * Get the server status instance which controls this dispatcher.
     * @return The server status instance.
     */
    public ServerStatus getServerStatus() {
        return _status;
    }

    private boolean openServerSocket() {
        try {
            _serverSocket = Communication.getServerSocket(_address.getPort());
        } catch (IOException ex) {
            //exit if we cant get a connection
            return false;
        }
        return true;
    }

    private void closeServerSocket() {
        try {
            _serverSocket.close();
        } catch (IOException ex) {
            //nothing left to do with a broken socket
        }
    }

    @Override
    public void run() {

        if (!openServerSocket())
            return; //if we couldn't open the socket then return.
        
        Socket socket;

        while (_status.running()) {

            try {
                socket = _serverSocket.accept();
                socket.setSoTimeout(Communication.TIMEOUT);
                RequestThread requestThread = _baseThread.getCopy();
                requestThread.setDispatcher(this);
                requestThread.setSocket(socket);
                _threadPool.add(requestThread);
                requestThread.start(); //handle the request

            } catch (IOException ex) {
                //if we couldn't open the connection then try again
                if (!_serverSocket.isBound()) //if the socket closed then try to reopen it
                    if (!openServerSocket())
                        return;
            }

        }

        closeServerSocket();

        closeOpenThreads();
    }
    
    /**
     * Remove a request thread from the thread pool.
     * @param requestThread The thread to remove.
     */
    public void removeRequestFromPool(RequestThread requestThread) {
    	_threadPool.remove(requestThread);
    }
    
    //wait for all the threads in the pool to close
    private void closeOpenThreads() {
        //System.out.println("closing open request threads");
        for (RequestThread thread : _threadPool) {
            try {
                thread.join();
            } catch (InterruptedException ex) {
                //if this thread is interupted anyway then continue to other threads
            }
        }

    }
}
